package gateway

import (
	"context"
	"net/http"
	"net/url"
	"sync"

	v1 "code.byted.org/bytertc/whiteboard_server/biz/api/pb/v1"

	"code.byted.org/gopkg/env"
	"code.byted.org/middleware/hertz/byted/discovery"
	"code.byted.org/middleware/hertz/pkg/common/config"
	"code.byted.org/middleware/hertz/pkg/protocol"
	"github.com/bytedance/sonic"

	"code.byted.org/middleware/hertz/pkg/app"

	bytedtracer "code.byted.org/bytedtrace/interface-go"
	"code.byted.org/bytertc/whiteboard_server/biz/pkg/trace"
	"code.byted.org/bytertc/whiteboard_server/kitex_gen/toutiao/videoarch/rtc_api_v2"
	"code.byted.org/bytertc/whiteboard_server/kitex_gen/toutiao/videoarch/rtc_api_v2/rtcapi"
	"code.byted.org/gopkg/logs/v2/log"
	"code.byted.org/middleware/hertz/byted"
	"github.com/pkg/errors"
)

const fromServer = "server"
const fromBid = "wb_bil_vflter_20220909_bd"

// openAPI headers
const (
	AccountID     = "X-Top-Account-Id"
	Action        = "Action"
	Version       = "Version"
	Service       = "X-Top-Service"
	Region        = "X-Top-Region"
	RequestID     = "X-Top-Request-Id"
	ConsoleSource = "X-Rtc-Console-Source"
	ForwardIP     = "X-Forwarded-For"
)

type RtcAPI interface {
	GetAccountID(ctx context.Context, appID string) (string, error)
	StartWebCast(ctx context.Context, params *StartWebCastParams) (err error)
	StopWebCast(ctx context.Context, params *StopWebCastParams) (err error)
	StartRecord(ctx context.Context, params *StartRecordParams) (err error)
	StopRecord(ctx context.Context, params *StopRecordParams) (err error)
	ToUser(ctx context.Context, appID, roomID, userID, message, tag string, p2pType *v1.OnWireRequest_RtsP2PType) (err error, errCode string)
	ToRoom(ctx context.Context, appID, roomID, message, tag string) (err error, errCode string)
	GenByteToken(ctx context.Context, appID, roomID, userID string) (token string, err error)
}

func NewRtcApi(rtcApiClient rtcapi.Client, httpClient *byted.Client, recordURL, transcodeURL, rtmURL, tokenURL string) *rtcApi {
	if recordURL == "" || transcodeURL == "" || rtmURL == "" {
		panic(errors.Errorf("url is empty"))
	}
	return &rtcApi{
		httpClient:   httpClient,
		client:       rtcApiClient,
		recordURL:    recordURL,
		transcodeURL: transcodeURL,
		rtmURL:       rtmURL,
		tokenURL:     tokenURL,
	}
}
func GetURL(params ...string) string {
	if len(params) == 0 {
		return ""
	}
	ret := "http://"
	for _, param := range params {
		ret += param
	}
	return ret
}
func GetCommonResponseMetadata(c *app.RequestContext) *ResponseMetadata {
	data := &ResponseMetadata{
		RequestId: string(c.GetHeader(RequestID)),
		Service:   string(c.GetHeader(Service)),
		Region:    string(c.GetHeader(Region)),
		Action:    c.Query(Action),
		Version:   c.Query(Version),
	}
	return data
}

type rtcApi struct {
	httpClient   *byted.Client
	client       rtcapi.Client
	accountMap   sync.Map // app id -> account id
	recordURL    string
	transcodeURL string
	rtmURL       string
	tokenURL     string
}

func (r *rtcApi) GetAccountID(ctx context.Context, appID string) (string, error) {
	if value, ok := r.accountMap.Load(appID); ok {
		ret, _ := value.(string)
		return ret, nil
	}

	getAccountParam := rtc_api_v2.NewTGetAppIDsInfoParam()
	getAccountParam.SetAppIDs([]string{appID})
	getAccountResp, err := r.client.GetAppIdsInfo(ctx, getAccountParam)
	if err != nil {
		return "", errors.Wrapf(err, "fail to get accountID of appID %s", appID)
	}
	infos := getAccountResp.GetAppIDInfos()
	if len(infos) != 1 {
		return "", errors.Errorf("fail to get accountID of appID %s, resp: %v", appID, getAccountResp)
	}
	r.accountMap.Store(appID, infos[0].Owner)
	return infos[0].Owner, nil

}

func (r *rtcApi) StartWebCast(ctx context.Context, params *StartWebCastParams) (err error) {
	span, ctx := bytedtracer.StartCustomSpan(ctx, trace.INTERNAL, trace.StartWebCast, bytedtracer.AsAsyncChildSpan)
	defer func() {
		span.Finish()
		if err != nil {
			log.V1.CtxError(ctx, "fail to StartWebCast %+v", err)
		}
	}()
	queryVersionValue := "2020-12-01"
	action := "StartWebCast"
	accountId, err := r.GetAccountID(ctx, params.AppId)
	if err != nil {
		return err
	}
	_, err, _ = r.do(ctx, params, r.transcodeURL+"start", queryVersionValue, action, accountId)
	return err
}

func (r *rtcApi) StopWebCast(ctx context.Context, params *StopWebCastParams) (err error) {
	span, ctx := bytedtracer.StartCustomSpan(ctx, trace.INTERNAL, trace.StopWebCast, bytedtracer.AsAsyncChildSpan)
	defer func() {
		span.Finish()
		if err != nil {
			log.V1.CtxError(ctx, "fail to StopWebCast %+v", err)
		}
	}()
	queryVersionValue := "2020-12-01"
	action := "StopWebCast"
	accountId, err := r.GetAccountID(ctx, params.AppId)
	if err != nil {
		return err
	}
	_, err, _ = r.do(ctx, params, r.transcodeURL+"stop", queryVersionValue, action, accountId)
	return err
}

func (r *rtcApi) StartRecord(ctx context.Context, params *StartRecordParams) (err error) {
	span, ctx := bytedtracer.StartCustomSpan(ctx, trace.INTERNAL, trace.StartRecord, bytedtracer.AsAsyncChildSpan)
	defer func() {
		span.Finish()
		if err != nil {
			log.V1.CtxError(ctx, "fail to StartRecord %+v", err)
		}
	}()
	queryVersionValue := "2022-06-01"
	action := "StartRecord"
	accountId, err := r.GetAccountID(ctx, params.AppId)
	if err != nil {
		return err
	}
	log.V1.CtxInfo(ctx, "StartRecord params:%+v", params)
	_, err, _ = r.do(ctx, params, r.recordURL+"start", queryVersionValue, action, accountId)
	return err
}

func (r *rtcApi) StopRecord(ctx context.Context, params *StopRecordParams) (err error) {
	span, ctx := bytedtracer.StartCustomSpan(ctx, trace.INTERNAL, trace.StopRecord, bytedtracer.AsAsyncChildSpan)
	defer func() {
		span.Finish()
		if err != nil {
			log.V1.CtxError(ctx, "fail to StopRecord %+v", err)
		}
	}()
	queryVersionValue := "2022-06-01"
	action := "StopRecord"
	accountId, err := r.GetAccountID(ctx, params.AppId)
	if err != nil {
		return err
	}
	_, err, _ = r.do(ctx, params, r.recordURL+"stop", queryVersionValue, action, accountId)
	return err
}

func (r *rtcApi) ToUser(ctx context.Context, appID, roomID, userID, message, tag string, p2pType *v1.OnWireRequest_RtsP2PType) (err error, errCode string) {
	span, ctx := bytedtracer.StartCustomSpan(ctx, trace.INTERNAL, trace.RtmUnicast, bytedtracer.AsAsyncChildSpan)
	defer func() {
		span.Finish()
		if err != nil {
			log.V1.CtxError(ctx, "fail to send %+v,errCode:%s", err, errCode)
		}
	}()

	param := &sendUnicastParam{
		AppID:   appID,
		From:    fromBid + "-" + tag,
		To:      userID,
		Binary:  true,
		Message: message,
	}
	queryVersionValue := "2020-12-01"
	accountID, err := r.GetAccountID(ctx, appID)
	if err != nil {
		return err, ""
	}
	log.V1.CtxInfo(ctx, "sending message to user %s:%s:%s, accountId %s, len : %d",
		appID, roomID, userID, accountID, len(message))
	//兼容rtc+rts
	if p2pType != nil && *p2pType == v1.OnWireRequest_kOutOfRoom {
		_, err, errCode = r.do(ctx, param, r.rtmURL, queryVersionValue, "SendUnicast", accountID)
		return err, errCode
	} else {
		roomUnicastParam := &sendRoomUnicastParam{
			sendUnicastParam: *param,
			RoomID:           roomID,
		}
		_, err, errCode = r.do(ctx, roomUnicastParam, r.rtmURL, queryVersionValue, "SendRoomUnicast", accountID)
		return err, errCode
	}
}

func (r *rtcApi) ToRoom(ctx context.Context, appID, roomID, message, tag string) (err error, errCode string) {
	span, ctx := bytedtracer.StartCustomSpan(ctx, trace.INTERNAL, trace.RtmBroadcast, bytedtracer.AsAsyncChildSpan)
	defer func() {
		span.Finish()
		if err != nil {
			log.V1.CtxError(ctx, "fail to send %+v,errCode:%s", err, errCode)
		}
	}()

	param := &sendBroadcastParam{
		AppID:   appID,
		RoomID:  roomID,
		From:    fromBid + "-" + tag,
		Binary:  true,
		Message: message,
	}
	queryVersionValue := "2020-12-01"
	accountID, err := r.GetAccountID(ctx, appID)
	if err != nil {
		return err, ""
	}
	log.V1.CtxInfo(ctx, "sending message to room %s:%s, accountId %s, len : %d",
		appID, roomID, accountID, len(message))
	_, err, errCode = r.do(ctx, param, r.rtmURL, queryVersionValue, "SendBroadcast", accountID)
	return err, errCode
}

func (r *rtcApi) GenByteToken(ctx context.Context, appID, roomID, userID string) (token string, err error) {
	span, ctx := bytedtracer.StartCustomSpan(ctx, trace.INTERNAL, trace.RtmBroadcast, bytedtracer.AsAsyncChildSpan)
	defer func() {
		span.Finish()
		if err != nil {
			log.V1.CtxError(ctx, "fail to GenByteToken %+v", err)
		}
	}()
	param := &genTokenParam{
		AppID:  appID,
		RoomID: roomID,
		UserID: userID,
	}
	queryVersionValue := "2018-01-01"
	accountID, err := r.GetAccountID(ctx, appID)
	if err != nil {
		return "", err
	}
	ret, err, _ := r.do(ctx, param, r.tokenURL, queryVersionValue, "GenToken", accountID)
	if err != nil {
		return "", err
	}
	log.V1.CtxInfo(ctx, "ret:%+v", ret)
	result, ok := ret.(map[string]interface{})
	token, exist := result["Token"].(string)
	if !ok || !exist || token == "" {
		return "", errors.Errorf("result token get fail")
	}
	return token, nil

}

func (r *rtcApi) do(ctx context.Context, body interface{}, requestUrl, queryVersionValue, action, accountId string) (interface{}, error, string) {
	data, _ := sonic.Marshal(body)
	query := url.Values{}
	query.Add(Action, action)
	query.Add(Version, queryVersionValue)

	req := &protocol.Request{}
	req.SetOptions(discovery.WithDestinationCluster(env.Cluster()))
	req.SetMethod(http.MethodPost)
	req.SetBody(data)
	req.SetRequestURI(requestUrl)
	req.SetOptions(config.WithSD(true))
	req.SetHeader(AccountID, accountId)
	req.SetQueryString(query.Encode())
	req.SetHeader("Content-Type", "application/json")

	resp := &protocol.Response{}
	err := r.httpClient.Do(ctx, req, resp)
	if err != nil {
		return nil, err, ""
	}
	ret := &CommonResponse{}
	err = sonic.Unmarshal(resp.Body(), ret)
	if err != nil {
		return nil, errors.WithMessage(err, "fail to unmarshal"), ""
	}
	if ret.Result == nil {
		if ret.ResponseMetadata.Error == nil {
			return nil, errors.New("error is empty"), ""
		}
		if ret.ResponseMetadata.Error.Message != "" {
			return nil, errors.New(ret.ResponseMetadata.Error.Message), ret.ResponseMetadata.Error.Code
		} else {
			return nil, errors.New(ret.ResponseMetadata.Error.Code), ret.ResponseMetadata.Error.Code
		}

	}
	return ret.Result, nil, ""
}
